#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <unistd.h>

#include "ipc_api.h"

#define IPC_LOCK_SEM_NUM 1

typedef struct{
	int semId;
	int semIndex;
}lockContext_s; 
static lockContext_s lockContext = {0,0};

ipcRet_e ipcLockInit(int create)
{
	ipcRet_e ret = ipcSuccess;
	int flag = 0666 |IPC_CREAT ;
	int semRet = 0;
	key_t key = -1;

	key = ftok(FTOK_PATH_NAME,FTOK_SEMAPHORE_PROJ_ID);
	if(TRUE == create)
		flag |= IPC_EXCL;
	
	lockContext.semId = semget(key, IPC_LOCK_SEM_NUM,flag);
	if(-1 == lockContext.semId)
	{
		return ipcErrorSyscall;
	}

	if(FALSE == create)
	{
		return ret;
	}
	
	semRet = semctl(lockContext.semId,lockContext.semIndex,SETVAL,0);
	if(semRet == -1)
	{
		ipcLogDebug("func %s semctl failed lockContext.semId %d \n",
									__FUNCTION__,lockContext.semId);
		return ipcErrorSyscall;
	}

	return ret;
}

ipcRet_e ipcLockDeinit(int create)
{
	ipcRet_e ret = ipcSuccess;
	int semRet = 0;

	if(FALSE == create)
	{
		return ret;
	}

	if(pIPCHandle->lockHoldPid != IPC_INVALID_PID)
	{
		ipcLogError("func %s ipc lock hold by pid %d name %s\n",__FUNCTION__,
								pIPCHandle->lockHoldPid,pIPCHandle->lockHoldName);
		return ipcErrorMisc;
	}
	
	semRet = semctl(lockContext.semId,lockContext.semIndex,IPC_RMID);
	if(semRet == -1)
	{
		ipcLogError("func %s semctl failed semId %d semIndex %d  \n",__FUNCTION__,
				lockContext.semId,lockContext.semIndex);
		return ipcErrorSyscall;
	}
	
	lockContext.semId = lockContext.semIndex = -1;
	
	return ret;
}

ipcRet_e ipcLock(ipcLockContext_s *pContext)
{
	ipcRet_e ret = ipcSuccess;
	int semRet = 0;
	struct sembuf sem[2];

	if(pIPCHandle->lockHoldPid != IPC_INVALID_PID)
	{
		if(NULL != pContext  && NULL != pContext->callerFunctionName)
		{
			ipcLogDebug("ipcLock failed func %s pid %d hold lock,func %s pid %d request lock\n",
							pIPCHandle->lockHoldName,pIPCHandle->lockHoldPid,
							pContext->callerFunctionName,pContext->callerPid);
		}
		return ipcErrorTimeout;
	}
	
	sem[0].sem_num = lockContext.semIndex;
	sem[0].sem_op = 0;
	sem[0].sem_flg = 0;

	sem[1].sem_num = lockContext.semIndex;
	sem[1].sem_op = 1;
	sem[1].sem_flg = SEM_UNDO;

	while(1)
	{
		semRet = semop(lockContext.semId, &sem[0],sizeof(sem)/sizeof(sem[0]));
		
		if(0 == semRet)//hold lock
		{
			pIPCHandle->lockHoldPid = getpid();
			
			if(NULL != pContext  && NULL != pContext->callerFunctionName)
			{
				strncpy(pIPCHandle->lockHoldName,pContext->callerFunctionName,
									sizeof(pIPCHandle->lockHoldName));
				
				ipcLogDebug("func %s pid %d hold the lock \n",
								pIPCHandle->lockHoldName,pIPCHandle->lockHoldPid);
			}
			
			break;
		}
		else //failed
		{
			if(errno == EINTR)
			{
				continue;
			}
			else		
			{
				ipcLogError("ipcLock failed semop\n");
				perror("ipcLock semop\n");
				ret = ipcErrorMisc;
				
				break;
			}
		}
	}
	
	return ret;
}

ipcRet_e ipcUnlock(ipcLockContext_s *pContext)
{
	ipcRet_e ret = ipcSuccess;
	int semRet = 0;
	struct sembuf sem[1];
	
	if(IPC_INVALID_PID == pIPCHandle->lockHoldPid)
	{
		if(NULL != pContext  && NULL != pContext->callerFunctionName)
		{
			ipcLogError("pcUnlock failed:onbody hold lock,but func %s pid %d release the lock\n",
							pContext->callerFunctionName,pContext->callerPid);
		}

		return ipcErrorMisc;
	}

	sem[0].sem_num = lockContext.semIndex;
	sem[0].sem_op = -1;
	sem[0].sem_flg = SEM_UNDO;

	
	pIPCHandle->lockHoldPid = IPC_INVALID_PID;
	PUT_ZERO_2_MEMORY(pIPCHandle->lockHoldName,sizeof(pIPCHandle->lockHoldName));

	semRet = semop(lockContext.semId, &sem[0],sizeof(sem)/sizeof(sem[0]));
	if(semRet == -1)
	{
		ipcLogError("ipcUnlock semop failed\n");
		perror("ipcUnlock semop\n");
		ret = ipcErrorMisc;
	}
	else//release the lock
	{
		if(NULL != pContext  && NULL != pContext->callerFunctionName)
		{
			ipcLogDebug("func %s pid %d release the lock\n",
								pContext->callerFunctionName,pContext->callerPid);
		}
	}
	
	return ret;
}

